/*
 * Copyright (c) 2020-2099 the original author or authors. All rights reserve.
 */
package org.tianyun.cloud.amqp.rabbitmq;

import org.springframework.amqp.rabbit.config.RabbitListenerConfigUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.util.StringUtils;
import org.tianyun.cloud.amqp.rabbitmq.autoconfigure.RabbitMqAutoConfiguration;
import org.tianyun.cloud.amqp.rabbitmq.factory.ManagedRabbitConnectionFactory;
import org.tianyun.cloud.amqp.rabbitmq.factory.ManagedRabbitListenerContainerFactory;
import org.tianyun.cloud.amqp.rabbitmq.message.AfterReceivePostProcessor;
import org.tianyun.cloud.amqp.rabbitmq.message.BeforePublishPostProcessor;
import org.tianyun.cloud.amqp.rabbitmq.message.RabbitMessageConfirmAndReturn;
import org.tianyun.cloud.config.refresh.smart.SmartBeanDefinition;

import java.util.Map;

/**
 * RabbitMQ装载配置器
 *
 * @auther ebert_chan
 */
public class RabbitMqConnectionFactoryConfigurer {

    private static final String CONNECTION_FACTORY_NAME_SUFFIX = ".rabbitConnectionFactory";

    private RabbitContainerBeanWrapper rabbitContainerBeanWrapper;

    public RabbitMqConnectionFactoryConfigurer(String propertyBeanName, Map<String, Object> attributes) {
        rabbitContainerBeanWrapper = new RabbitContainerBeanWrapper();
        rabbitContainerBeanWrapper.setContainerFactoryBeanName((String) attributes.get("containerFactory"));
        rabbitContainerBeanWrapper.setConnectionFactoryBeanName((String) attributes.get("connectionFactory"));
        rabbitContainerBeanWrapper.setRabbitTemplateBeanName((String) attributes.get("rabbitTemplate"));
        rabbitContainerBeanWrapper.setPropertyBeanName(propertyBeanName);
        rabbitContainerBeanWrapper.setRefreshKey((String) attributes.get("prefix"));
        rabbitContainerBeanWrapper.setPrimary((Boolean) attributes.get("primary"));
        rabbitContainerBeanWrapper.setMessageConverterBeanName(RabbitMqAutoConfiguration.MESSAGE_CONVERTER_BEAN);
        if (!StringUtils.hasText(rabbitContainerBeanWrapper.getConnectionFactoryBeanName())) {
            // 自动定义一个beanName
            rabbitContainerBeanWrapper.setConnectionFactoryBeanName(propertyBeanName + CONNECTION_FACTORY_NAME_SUFFIX);
        }
    }

    public RabbitContainerBeanWrapper getRabbitContainerBeanWrapper() {
        return rabbitContainerBeanWrapper;
    }

    public void registryBean(BeanDefinitionRegistry registry) {
        if (!registry.containsBeanDefinition(rabbitContainerBeanWrapper.getPropertyBeanName())) {
            return;
        }

        BeanDefinition cachingConnectionFactory = this.connectionFactoryBeanDefinition(rabbitContainerBeanWrapper.getPropertyBeanName());
        this.setPrimary(cachingConnectionFactory, rabbitContainerBeanWrapper.getPrimary());
        // 使用SmartBeanDefinition，根据refreshKey动态刷新
        SmartBeanDefinition proxyCachingConnectionFactory = new SmartBeanDefinition(rabbitContainerBeanWrapper.getRefreshKey(), cachingConnectionFactory);
        proxyCachingConnectionFactory.registerBeanDefinition(registry, rabbitContainerBeanWrapper.getConnectionFactoryBeanName());

        if (StringUtils.hasText(rabbitContainerBeanWrapper.getContainerFactoryBeanName())) {
            BeanDefinition simpleRabbitListenerContainerFactory = this.simpleRabbitListenerContainerFactoryBeanDefinition(rabbitContainerBeanWrapper.getPropertyBeanName(), rabbitContainerBeanWrapper.getConnectionFactoryBeanName(), rabbitContainerBeanWrapper.getMessageConverterBeanName());
            this.setPrimary(simpleRabbitListenerContainerFactory, rabbitContainerBeanWrapper.getPrimary());
            registry.registerBeanDefinition(rabbitContainerBeanWrapper.getContainerFactoryBeanName(), simpleRabbitListenerContainerFactory);
        }

        if (StringUtils.hasText(rabbitContainerBeanWrapper.getRabbitTemplateBeanName())) {
            BeanDefinition rabbitTemplate = this.rabbitTemplateBeanDefinition(rabbitContainerBeanWrapper.getConnectionFactoryBeanName(), rabbitContainerBeanWrapper.getMessageConverterBeanName());
            this.setPrimary(rabbitTemplate, rabbitContainerBeanWrapper.getPrimary());
            registry.registerBeanDefinition(rabbitContainerBeanWrapper.getRabbitTemplateBeanName(), rabbitTemplate);
        }
    }

    private void setPrimary(BeanDefinition beanDefinition, Boolean primary) {
        if (primary != null && primary) {
            beanDefinition.setAutowireCandidate(true);
            beanDefinition.setPrimary(true);
        }
    }

    private BeanDefinition connectionFactoryBeanDefinition(String propertyBeanName) {
        // @formatter:off
		BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ManagedRabbitConnectionFactory.class)
                .addConstructorArgReference(propertyBeanName);
		// @formatter:on
        return builder.getBeanDefinition();
    }

    private BeanDefinition simpleRabbitListenerContainerFactoryBeanDefinition(String propertyBeanName, String connectionFactoryBeanName, String messageConverterBeanName) {
        // @formatter:off
		BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ManagedRabbitListenerContainerFactory.class)
                .addConstructorArgReference(propertyBeanName)
                .addConstructorArgReference(RabbitListenerConfigUtils.RABBIT_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME)
				.addPropertyReference("messageConverter", messageConverterBeanName)
				.addPropertyReference("connectionFactory", connectionFactoryBeanName)
				.addDependsOn(messageConverterBeanName)
				.addDependsOn(connectionFactoryBeanName);
		// @formatter:on
        return builder.getBeanDefinition();
    }

    private BeanDefinition rabbitTemplateBeanDefinition(String connectionFactoryBeanName, String messageConverterBeanName) {
        RabbitMessageConfirmAndReturn rabbitMessageConfirmAndReturn = this.rabbitMessageConfirmAndReturn();
        // @formatter:off
		BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RabbitTemplate.class)
				.addPropertyReference("connectionFactory", connectionFactoryBeanName)
				.addPropertyReference("messageConverter", messageConverterBeanName)
				.addPropertyValue("mandatory", true)
				.addPropertyValue("confirmCallback", rabbitMessageConfirmAndReturn)
				.addPropertyValue("returnsCallback", rabbitMessageConfirmAndReturn)
				.addPropertyValue("beforePublishPostProcessors", new BeforePublishPostProcessor())
				.addPropertyValue("afterReceivePostProcessors", new AfterReceivePostProcessor())
				.addDependsOn(messageConverterBeanName)
				.addDependsOn(connectionFactoryBeanName);
		// @formatter:on
        return builder.getBeanDefinition();
    }

    private RabbitMessageConfirmAndReturn rabbitMessageConfirmAndReturn() {
        return new RabbitMessageConfirmAndReturn();
    }

}
